今天來寫一下 api union test
這是會用 vitest
與 prisma
做一些 mock data
的 test case
那我們廢話不多說馬上來看怎麼做的吧~
先 install
以下的 package
> npm i @vitejs/plugin-react
> npm i vite-tsconfig-paths
> npm i -D vitest
在 package.json
中加 scripts
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest"
},
在專案新增 vitest.config.js
// vitest.config.js
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
test: {
globals: true,
},
plugins: [tsconfigPaths(), react()],
});
globals: true
大家如果寫過 test
就知道每次要用 describe
或是 it
都要 import
。
import { beforeEach, describe, expect, it } from "vitest";
describe('trpc api', () => {
//..
設定成 globals: true
則告訴 test
只要看到 describe
就是用 vitest
的 lib
describe('trpc api', () => {
//..
哪因為我們這次的 test case
打算用 mock data
的形式,我們不希望因為跑 test case
直接就更改到 db
資料,所以我們需要透過 mock data
寫法。
vitest
有提供 mock
套件,然後請讀者 install
下面得 lib
。
npm i -D vitest-mock-extended
接著我們把 prisma
mock
起來,prismaMock
就是取代原本的 PrismaClient
的 instance
。
例外不要忘記我們要mock
的 prisma
位置這邊我們是放在 ../db
中,vi.restoreAllMocks()
以及 mock
之後要記得 restore
,確保比次的 test case
都是獨立的 data
import { PrismaClient } from '@prisma/client'
import { beforeEach, vi } from 'vitest'
import { mockDeep } from 'vitest-mock-extended'
vi.mock('../db')
beforeEach(() => {
vi.restoreAllMocks()
})
export const prismaMock = mockDeep<PrismaClient>()
原本我們不是透過 createTRPCContext
create context
嗎,因為 test
關西我們要額外把 session
拿出來。
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts
const session = await getServerAuthSession({ req, res });
return {
session: opts.session,
prisma,
};
};
這樣我們就可以透過 createInnerTRPCContext function
create session return
了。
type CreateContextOptions = {
session: Session | null;
};
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
prisma,
};
};
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts
const session = await getServerAuthSession({ req, res });
return createInnerTRPCContext({ session });
};
之後新增 ~src/server/api/_test_/index.test.ts
,然後把會用到的套件跟 mock data
先透過 faker
幫我們隨機生成。
mockData
: user
資料。mockUserSession
: session
內容。
import { AppRouter, appRouter } from "../root";
import { inferProcedureInput } from "@trpc/server";
import { createInnerTRPCContext } from "../trpc";
import { prismaMock } from "@/server/__mocks__/prisma";
import { faker } from '@faker-js/faker'
describe('trpc api', () => {
let mockData =
{
"id": faker.number.int(),
"title": faker.lorem.slug(),
"content": faker.lorem.paragraph(2),
"published": faker.datatype.boolean(0.5),
"createdAt": faker.date.anytime(),
"updatedAt": faker.date.anytime(),
"userId": faker.string.uuid(),
}
let mockUserSession = {
user: {
id: faker.string.uuid()
},
expires: faker.date.anytime().toString()
}
let ctx = createInnerTRPCContext({
session: mockUserSession
})
開始之前先簡單測試一下 greeting api
會不會 pass
。
describe('trpc api', () => {
//..
it('greeting input', async () => {
type GreetInput = inferProcedureInput<AppRouter['greeting']>
const input: GreetInput = {
name: 'Danny'
}
const results = await caller.greeting(input)
expect(results).toStrictEqual('hello Danny')
})
成功~~
trpc
有一個 Caller
用法讓你可以在 server api
與 test
中 return data
,caller
用法就是呼叫一個 function
當作 call api
,這也是 RPC
架構的精髓~透過 caller
就可以把 api
邏輯包成一個 function
~
import { AppRouter, appRouter } from "../root";
import { prismaMock } from "@/server/__mocks__/prisma";
describe('trpc api', () => {
//..
const caller = appRouter.createCaller({ session: ctx.session, prisma: prismaMock })
因為我們的 caller
有把 mock session
資料放進去,所以以下的 results
會試 authorized
的狀態。
透過 prismaMock
實作 mockResolvedValue
讓 caller.posts.getPosts
的結果會是 mockDatas
資料,接著我們速速 demo
其他的 api
~
describe('trpc api', () => {
//..
it('getPosts should return equal mockData', async () => {
const mockDatas = [mockData]
prismaMock.post.findMany.mockResolvedValue(mockDatas)
const results = await caller.posts.getPosts()
expect(results).toHaveLength(mockDatas.length)
expect(results).toStrictEqual(mockDatas)
})
it('success create Post', async () => {
prismaMock.post.create.mockResolvedValue(mockData)
const result = await caller.posts.addPost({
title: mockData.title,
content: mockData.content
})
expect(result).toStrictEqual({
message: 'success create post',
id: mockData.id
})
})
it('success toggle post published type', async () => {
prismaMock.post.update.mockResolvedValue(mockData)
const result = await caller.posts.togglePostPublish({ id: mockData.id, published: !mockData.published })
expect(result).toStrictEqual({
message: 'success update post',
id: mockData.id
})
})
it('success delete post ', async () => {
prismaMock.post.delete.mockResolvedValue(mockData)
const result = await caller.posts.deletePost({ id: mockData.id })
expect(result).toStrictEqual({
message: 'success delete post',
id: mockData.id
})
})
it('success get infinite Posts ', async () => {
prismaMock.post.findMany.mockResolvedValue([mockData])
const result = await caller.posts.infinitePosts({ limit: 10 })
console.log(result)
expect(result).toStrictEqual({
posts: [mockData],
nextCursor: undefined
})
})
這樣我們就把 CRUD
的 test
都完成拉~是不是很簡單呢~ 最後我們 run
npm run test
看一下結果~
All pass ~
這樣各位小夥伴應該就會寫 api
的 mock test
了 ~
https://tawaldevuniverse.hashnode.dev/some-tips-when-using-t3-stack-unit-testing-with-trpc-procedures-environment-setup#heading-final-setup
https://www.prisma.io/blog/testing-series-1-8eRB5p0Y8o